package com.piece.core.pay.client.impl.wxpay;

import com.alibaba.fastjson.JSONObject;
import com.piece.core.framework.exception.ErrorCode;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.net.HtmlUtil;
import com.piece.core.framework.util.object.ObjectUtil;
import com.piece.core.framework.util.security.coder.Base64Coder;
import com.piece.core.framework.util.security.coder.RSACoder;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.framework.util.time.DateUtil;
import com.piece.core.pay.client.AbstractPayClient;
import com.piece.core.pay.dto.PayNotifyDataDTO;
import com.piece.core.pay.dto.PayOrderNotifyRespDTO;
import com.piece.core.pay.dto.PayRefundNotifyDTO;
import com.piece.core.pay.dto.PayRefundUnifiedReqDTO;
import com.piece.core.pay.enums.PayNotifyRefundStatusType;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.util.IOUtil;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.ReqFundsAccount;
import com.wechat.pay.java.service.weixinpayscanandride.WeixinPayScanAndRideService;
import com.wechat.pay.java.service.weixinpayscanandride.model.QueryTransactionRequest;
import com.wechat.pay.java.service.weixinpayscanandride.model.TradeState;
import com.wechat.pay.java.service.weixinpayscanandride.model.TransactionsEntity;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.*;
import java.util.ArrayList;

@Slf4j
public abstract class AbstractWxPayClient extends AbstractPayClient<WXPayClientConfig> {

    protected RefundService refundService;
    protected WeixinPayScanAndRideService rideService;

    public AbstractWxPayClient(String channelId, String channelCode, WXPayClientConfig config) {
        super(channelId, channelCode, config, new WXResponseMapping());
    }

    protected String getSerialNumber(InputStream certInputStream) {
        String serialNo = null;
        // 获取证书序列号
        X509Certificate certificate = getCertificate(certInputStream);
        if (null != certificate) {
            serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
        }
//        log.info("证书序列号:" + certificate.getSerialNumber().toString(16));
//        log.info("版本号:" + certificate.getVersion());
//        log.info("签发者：" + certificate.getIssuerDN());
//        log.info("有效起始日期：" + certificate.getNotBefore());
//        log.info("有效终止日期：" + certificate.getNotAfter());
//        log.info("主体名：" + certificate.getSubjectDN());
//        log.info("签名算法：" + certificate.getSigAlgName());
//        log.info("签名：" + certificate.getSignature().toString());
        return serialNo;
    }

    protected X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            log.error("证书已过期", e);
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            log.error("证书尚未生效", e);
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            log.error("无效的证书", e);
            throw new RuntimeException("无效的证书", e);
        }
    }

    /**
     * 解析支付单的通知结果
     */
    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO notifyData) throws Exception {
        HttpServletRequest request = notifyData.getRequest();
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        String body = HtmlUtil.readData(request);

        String plainText = verifyNotify(serialNo, body, signature, nonce, timestamp,
                IOUtil.toString(config.getApiV3Key()), config.getPrivateCert());
        JSONObject jsonObject = JSONObject.parseObject(plainText);
        JSONObject payer = jsonObject.getJSONObject("payer");
        JSONObject amount = jsonObject.getJSONObject("amount");

        // 转换结果
        return PayOrderNotifyRespDTO.builder().orderExtensionNo(jsonObject.getString("out_trade_no"))
                .channelOrderNo(jsonObject.getString("transaction_id")).channelUserId(payer.getString("openid"))
                .amount(amount.getLong("payer_total"))
                .successTime(DateUtil.getDate(jsonObject.getString("success_time"), "yyyyMMddHHmmss"))
                .tradeStatus(jsonObject.getString("trade_state_desc"))
                .data(plainText).build();
    }

    /**
     * v3 支付异步通知验证签名
     *
     * @param serialNo        证书序列号
     * @param body            异步通知密文
     * @param signature       签名
     * @param nonce           随机字符串
     * @param timestamp       时间戳
     * @param key             api 密钥
     * @param certInputStream 平台证书
     */
    protected String verifyNotify(String serialNo, String body, String signature, String nonce,
                                      String timestamp, String key, InputStream certInputStream) throws Exception {
        // 获取平台证书序列号
        X509Certificate certificate = getCertificate(certInputStream);
        String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
        // 验证证书序列号
        if (serialNumber.equals(serialNo)) {
            boolean verifySignature = verifySignature(signature, body, nonce, timestamp, certificate.getPublicKey());
            if (verifySignature) {
                JSONObject jsonObject = JSONObject.parseObject(body);
                JSONObject resource = jsonObject.getJSONObject("resource");
                String cipherText = resource.getString("ciphertext");
                String nonceStr = resource.getString("nonce");
                String associatedData = resource.getString("associated_data");

                return decryptToString(key.getBytes(StandardCharsets.UTF_8),
                        associatedData.getBytes(StandardCharsets.UTF_8),
                        nonceStr.getBytes(StandardCharsets.UTF_8),
                        cipherText);
            } else {
                throw new Exception("签名错误");
            }
        } else {
            throw new Exception("证书序列号错误");
        }
    }

    /**
     * 退款实现接口
     */
    @Override
    protected AjaxResponse doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
        Refund response;
        try {
            CreateRequest request = new CreateRequest();
            request.setTransactionId(reqDTO.getChannelOrderNo());
            request.setOutTradeNo(reqDTO.getPayTradeNo());
            request.setOutRefundNo(reqDTO.getMerchantRefundId());
            request.setReason(reqDTO.getReason());
            // 设置退款金额
            AmountReq amount = new AmountReq();
            amount.setTotal(reqDTO.getAmount());
            request.setAmount(amount);
            request.setFundsAccount(ReqFundsAccount.AVAILABLE);
            request.setNotifyUrl(StringUtil.isEmpty(reqDTO.getNotifyUrl()) ? config.getRefundUrl() : reqDTO.getNotifyUrl());
            // 调用接口
            response = refundService.create(request);
        } catch (HttpException e) { // 发送HTTP请求失败
            log.error("[微信jsApi退款失败] 原因：{}", e);
            return AjaxResponse.result(ErrorCode.HTTP, e.getMessage());
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300，例如500
            log.error("[微信jsApi退款失败] 原因：{}", e);
            return AjaxResponse.result(codeMapping.apply(
                    ObjectUtil.defaultIfNull(e.getErrorCode(), "CustomErrorCode"),
                    ObjectUtil.defaultIfNull(e.getErrorMessage())), null);
        } catch (MalformedMessageException e) { // 服务返回成功，返回体类型不合法，或者解析返回体失败
            log.error("[微信jsApi退款失败] 原因：{}", e);
            return AjaxResponse.result(ErrorCode.ARGUMENT, "微信退款失败");
        }

        return AjaxResponse.result(codeMapping.apply(WXResponseMapping.CODE_SUCCESS, WXResponseMapping.MESSAGE_SUCCESS), response);
    }

    /**
     * 解析支付退款通知数据
     */
    @Override
    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) throws Exception {
        HttpServletRequest request = notifyData.getRequest();
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        String body = HtmlUtil.readData(request);

        String plainText = verifyNotify(serialNo, body, signature, nonce, timestamp,
                IOUtil.toString(config.getApiV3Key()), config.getPrivateCert());
        JSONObject jsonObject = JSONObject.parseObject(plainText);
        JSONObject amount = jsonObject.getJSONObject("amount");
        String status = jsonObject.getString("status");

        // 转换结果
        return PayRefundNotifyDTO.builder().tradeNo(jsonObject.getString("out_trade_no"))
                .channelOrderNo(jsonObject.getString("transaction_id")).reqNo(jsonObject.getString("out_refund_no"))
                .amount(amount.getLong("refund"))
                .refundSuccessTime(DateUtil.getDate(jsonObject.getString("success_time"), "yyyyMMddHHmmss"))
                .status(PayNotifyRefundStatusType.valueOf(status)).build();
    }

    /**
     * 验证签名
     *
     * @param signature 待验证的签名
     * @param body      应答主体
     * @param nonce     随机串
     * @param timestamp 时间戳
     * @param publicKey {@link PublicKey} 微信支付平台公钥
     */
    public boolean verifySignature(String signature, String body, String nonce, String timestamp, PublicKey publicKey) throws Exception {
        String buildSignMessage = buildSignMessage(timestamp, nonce, body);
        return RSACoder.verify256(buildSignMessage.getBytes(StandardCharsets.UTF_8), publicKey, signature);
    }

    /**
     * 构造签名串
     *
     * @param timestamp 应答时间戳
     * @param nonceStr  应答随机串
     * @param body      应答报文主体
     * @return 应答待签名字符串
     */
    public String buildSignMessage(String timestamp, String nonceStr, String body) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(timestamp);
        arrayList.add(nonceStr);
        arrayList.add(body);
        return buildSignMessage(arrayList);
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     */
    public String buildSignMessage(ArrayList<String> signMessage) {
        if (null == signMessage || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * 证书和回调报文解密
     *
     * @param aesKey         api 密钥
     * @param associatedData associated_data
     * @param nonce          nonce
     * @param cipherText     ciphertext
     * @return {String} 平台证书明文
     * @throws GeneralSecurityException 异常
     */
    protected String decryptToString(byte[] aesKey, byte[] associatedData, byte[] nonce, String cipherText) throws GeneralSecurityException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64Coder.decode(cipherText).getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 查询订单支付结果
     */
    @Override
    public PayOrderNotifyRespDTO tradeUnifiedQuery(String outTradeNo) {
        PayOrderNotifyRespDTO response;
        try {
            QueryTransactionRequest request = new QueryTransactionRequest();
            request.setOutTradeNo(outTradeNo);
            TransactionsEntity entity = rideService.queryTransaction(request);
            TradeState tradeState = entity.getTradeState();
            return PayOrderNotifyRespDTO.builder()
                    .orderExtensionNo(entity.getOutTradeNo())
                    .channelOrderNo(entity.getTransactionId())
                    .channelUserId(entity.getSubMchid())
                    .amount(entity.getAmount().getPayerTotal())
                    .successTime(DateUtil.getDate(entity.getSuccessTime(), "yyyyMMddHHmmss"))
                    .tradeStatus(entity.getTradeState().toString())
                    .data(entity.getTradeStateDescription()).build();
        } catch (Exception e) {
            log.error("[微信查询支付结果失败]：{})", e);
            return PayOrderNotifyRespDTO.builder().tradeStatus("FAIL").build();
        }
    }
}
